package FM2_15577.front_end;

import org.json.JSONArray;
import org.json.JSONObject;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.io.*;
import jxl.*;
import jxl.write.*;
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.PdfWriter;

public class BankSystem {
    private Account ac = null; // 当前登录的账户（用户模式登录）
    private String adminName; // 当前账户为管理员（管理员模式登录）
    private String result; // 存储最后一次调用功能接口的结果
    private String reason; // 存储最后一次调用功能接口的结果的原因
    private static Client client = new Client(); // 负责完成与服务器间通信

    public long getAccount() { // get method
        return this.ac.getAid();
    }

    public String getAdmin() { // get method
        return this.adminName;
    }

    // public String getResult() { // get method
    //     return this.result;
    // }

    // public String getReason() { // get method
    //     return this.reason;
    // }

    public long checkAndConvertAid(String s) throws Exception {
        if (s.length() != 10) throw new Exception("The length of account ID is not 10!");
        long ret;
        try {
            ret = Long.parseLong(s);
        } catch (Exception e) {
            throw new Exception("The account ID is not a number!");
        }
        if (s.charAt(0) < '1' || s.charAt(0) > '9') throw new Exception("The account ID contains leading 0 or minus sign!");
        return ret;
    }

    public String checkAndConvertAdminName(String s) throws Exception {
        if (s.length() > 20) throw new Exception("The length of the name exceeds 20!");
        return s;
    }

    public String checkAndConvertName(String s) throws Exception {
        if (s.length() > 10) throw new Exception("The length of the name exceeds 10!");
        return s;
    }

    public String checkAndConvertCipher(String s) throws Exception {
        if (s.length() < 4) throw new Exception("The length of the password is shorter than 4!");
        return s;
    }

    public long checkAndConvertID(String s) throws Exception {
        if (s.length() != 12) throw new Exception("The length of ID is not 12!");
        long ret;
        try {
            ret = Long.parseLong(s);
        } catch (Exception e) {
            throw new Exception("The ID is not a number!");
        }
        if (s.charAt(0) < '1' || s.charAt(0) > '9') throw new Exception("The ID contains leading 0 or minus sign!");
        return ret;
    }

    public long checkAndConvertPhone(String s) throws Exception {
        if (s.length() != 11) throw new Exception("The length of phone number is not 11!");
        long ret;
        try {
            ret = Long.parseLong(s);
        } catch (Exception e) {
            throw new Exception("The phone number is not a number!");
        }
        if (s.charAt(0) < '1' || s.charAt(0) > '9') throw new Exception("The phone number contains leading 0 or minus sign!");
        return ret;
    }

    public char checkAndConvertSex(String s) throws Exception {
        if (s.length() != 1) throw new Exception("The length of sex is not correct!");
        char ret = s.charAt(0);
        if (ret != 'M' && ret != 'F') throw new Exception("The sex is not correct (M and F are valid only)!");
        return ret;
    }

    public LocalDate checkAndConvertDob(String s) throws Exception {
        if (!s.matches("^\\d{4}-\\d{2}-\\d{2}$")) throw new Exception("The format of dob is not correct!");
        String[] parts = s.split("-");
        int y, m, d;
        try {
            y = Integer.parseInt(parts[0]);
        } catch (Exception e) {
            throw new Exception("Invalid year!");
        }
        try {
            m = Integer.parseInt(parts[1]);
        } catch (Exception e) {
            throw new Exception("Invalid month!");
        }
        try {
            d = Integer.parseInt(parts[2]);
        } catch (Exception e) {
            throw new Exception("Invalid day!");
        }
        LocalDate ret = LocalDate.of(y, m, d);
        if (ret.isAfter(LocalDate.now().minusYears(18))) throw new Exception("You're younger than 18!");
        if (ret.isBefore(LocalDate.now().minusYears(70))) throw new Exception("You're older than 70!");
        return ret;
    }

    public String checkAndConvertAmount(String s) throws Exception {
        if (s.contains(".")) {
            try {
                Double.parseDouble(s);
            } catch (Exception e) {
                throw new Exception("The format of decimal amount is not correct!");
            }
            if (!s.matches("^\\d*\\.\\d+$")) throw new Exception("The format of decimal amount is not correct!");
            String[] parts = s.split("\\.");
            if (parts[1].length() > 2) {
                throw new Exception("The length of the decimal part is greater than 2!");
            }
        }
        else {
            if (!s.matches("^\\d+$")) throw new Exception("The amount is not a integer!");
        }
        return new BigDecimal(s).toString();
    }

    public void registerAccount(String name, String cipher, long id, long phone, char sex, LocalDate dob) throws Exception {
        // System.err.println("Start: BankSystem.registerAccount()"); // debug
        JSONObject res = null; // 存储请求结果
        int createYear = LocalDate.now().getYear();
        cipher = client.SHA256(cipher); // 将密码转换为其SHA256结果以进行加密，防止明文密码传输
        // System.err.println(String.format("Info: %s, %s, %d, %d, %c, %s, %d", name, cipher, id, phone, sex, dob.toString(), createYear));
        JSONObject object = new JSONObject(); // 请求内容object
        object.put("type", "registerAccount"); // 请求类型
        // 请求附带信息
        object.put("name", name);
        object.put("cipher", cipher);
        object.put("id", id);
        object.put("phone", phone);
        object.put("sex", sex);
        object.put("dob", dob);
        object.put("createYear", createYear);
        try {
            res = client.send(object);
        } catch (Exception e) {
            // System.err.println(e.getMessage());
            throw e;
        }
        if (res == null) {
            // System.err.println("No response from server!");
            this.result = "failed";
            this.reason = "No response from server!";
            throw new Exception(this.reason);
        }
        else{
            this.result = res.getString("result");
            this.reason = res.getString("reason");
            if (res.getString("result").contentEquals("success")) {
                // System.err.println("Account created successfully");
                this.ac = new Account(res.getLong("aid"));
                // System.err.println("Current login account ID: "+this.getAccount());
            }
            else {
                // System.err.println("Account created failed: "+res.getString("reason"));
                throw new Exception(this.reason);
            }
        }
        // System.err.println("End: BankSystem.registerAccount()"); // debug
    }

    public void loginAccount(long aid, String cipher) throws Exception {
        // System.err.println("Start: BankSystem.loginAccount()"); // debug
        JSONObject res = null; // 存储请求结果
        cipher = client.SHA256(cipher); // 将密码转换为其SHA256结果以进行加密，防止明文密码传输
        // System.err.println(String.format("Info: %d, %s", aid, cipher));
        JSONObject object = new JSONObject(); // 请求内容object
        object.put("type", "loginAccount"); // 请求类型
        // 请求附带信息
        object.put("aid", aid);
        object.put("cipher", cipher);
        try {
            res = client.send(object);
        } catch (Exception e) {
            // System.err.println(e.getMessage());
            throw e;
        }
        if (res == null) {
            // System.err.println("No response from server!");
            this.result = "failed";
            this.reason = "No response from server!";
            throw new Exception(this.reason);
        }
        else{
            this.result = res.getString("result");
            this.reason = res.getString("reason");
            if (res.getString("result").contentEquals("success")) {
                // System.err.println("Login successfully");
                this.ac = new Account(aid);
                // System.err.println("Current login account ID: "+this.getAccount());
            }
            else {
                // System.err.println("Login failed: "+res.getString("reason"));
                throw new Exception(this.reason);
            }
        }
        // System.err.println("End: BankSystem.loginAccount()"); // debug
    }

    public void removeAccount(long aid) throws Exception { // for user
        // System.err.println("Start: BankSystem.removeAccount()"); // debug
        JSONObject res = null; // 存储请求结果
        JSONObject object = new JSONObject(); // 请求内容object
        object.put("type", "removeAccount"); // 请求类型
        // 请求附带信息
        object.put("aid", aid);
        try {
            res = client.send(object);
        } catch (Exception e) {
            // System.err.println(e.getMessage());
            throw e;
        }
        if (res == null) {
            // System.err.println("No response from server!");
            this.result = "failed";
            this.reason = "No response from server!";
            throw new Exception(this.reason);
        }
        else{
            this.result = res.getString("result");
            this.reason = res.getString("reason");
            if (res.getString("result").contentEquals("success")) {
                // System.err.println("Remove account successfully");
            }
            else {
                // System.err.println("Remove account failed: "+res.getString("reason"));
                throw new Exception(this.reason);
            }
        }
        // System.err.println("End: BankSystem.removeAccount()"); // debug
    }

    public void registerAdminAccount(String name, String cipher) throws Exception {
        // System.err.println("Start: BankSystem.registerAdminAccount()"); // debug
        JSONObject res = null; // 存储请求结果
        cipher = client.SHA256(cipher); // 将密码转换为其SHA256结果以进行加密，防止明文密码传输
        // System.err.println(String.format("Info: %s, %s", name, cipher));
        JSONObject object = new JSONObject(); // 请求内容object
        object.put("type", "registerAdminAccount"); // 请求类型
        // 请求附带信息
        object.put("name", name);
        object.put("cipher", cipher);
        try {
            res = client.send(object);
        } catch (Exception e) {
            // System.err.println(e.getMessage());
            throw e;
        }
        if (res == null) {
            // System.err.println("No response from server!");
            this.result = "failed";
            this.reason = "No response from server!";
            throw new Exception(this.reason);
        }
        else{
            this.result = res.getString("result");
            this.reason = res.getString("reason");
            if (res.getString("result").contentEquals("success")) {
                // System.err.println("Admin Account created successfully");
                this.adminName = name;
                // System.err.println("Current login admin name: "+name);
            }
            else {
                // System.err.println("Admin Account created failed: "+res.getString("reason"));
                throw new Exception(this.reason);
            }
        }
        // System.err.println("End: BankSystem.registerAdminAccount()"); // debug
    }

    public void loginAdminAccount(String name, String cipher) throws Exception {
        // System.err.println("Start: BankSystem.loginAdminAccount()"); // debug
        JSONObject res = null; // 存储请求结果
        cipher = client.SHA256(cipher); // 将密码转换为其SHA256结果以进行加密，防止明文密码传输
        // System.err.println(String.format("Info: %s, %s", name, cipher));
        JSONObject object = new JSONObject(); // 请求内容object
        object.put("type", "loginAdminAccount"); // 请求类型
        // 请求附带信息
        object.put("name", name);
        object.put("cipher", cipher);
        try {
            res = client.send(object);
        } catch (Exception e) {
            // System.err.println(e.getMessage());
            throw e;
        }
        if (res == null) {
            // System.err.println("No response from server!");
            this.result = "failed";
            this.reason = "No response from server!";
            throw new Exception(this.reason);
        }
        else{
            this.result = res.getString("result");
            this.reason = res.getString("reason");
            if (res.getString("result").contentEquals("success")) {
                // System.err.println("Login admin successfully");
                this.adminName = name;
                // System.err.println("Current login admin name: "+name);
            }
            else {
                // System.err.println("Login admin failed: "+res.getString("reason"));
                throw new Exception(this.reason);
            }
        }
        // System.err.println("End: BankSystem.loginAdminAccount()"); // debug
    }

    public BigDecimal getBalance() throws Exception {
        // System.err.println("Start: BankSystem.getBalance()"); // debug
        BigDecimal ret = null;
        try {
            ret = this.ac.getBalance();
            this.result = "success";
            // System.err.println("Current balance: "+ret.toString());
        } catch (Exception e) {
            this.result = "failed";
            this.reason = e.getMessage();
            // System.err.println(this.reason);
            throw e;
        }
        // System.err.println("End: BankSystem.getBalance()"); // debug
        return ret;
    }

    public BigDecimal deposit(String amount) throws Exception { // 返回存款后余额，amount为正实数且不超过两位小数
        // System.err.println("Start: BankSystem.deposit()"); // debug
        BigDecimal ret = null;
        try {
            ret = this.ac.deposit(amount);
            this.result = "success";
            // System.err.println("Current balance: "+ret.toString());
        } catch (Exception e) {
            this.result = "failed";
            this.reason = e.getMessage();
            // System.err.println(this.reason);
            throw e;
        }
        // System.err.println("End: BankSystem.deposit()"); // debug
        return ret;
    }

    public BigDecimal withdraw(String amount) throws Exception { // 返回取款后余额，amount为正实数且不超过两位小数
        // System.err.println("Start: BankSystem.withdraw()"); // debug
        BigDecimal ret = null;
        try {
            ret = this.ac.withdraw(amount);
            this.result = "success";
            // System.err.println("Current balance: "+ret.toString());
        } catch (Exception e) {
            this.result = "failed";
            this.reason = e.getMessage();
            // System.err.println(this.reason);
            throw e;
        }
        // System.err.println("End: BankSystem.withdraw()"); // debug
        return ret;
    }

    public BigDecimal transfer(long target, String amount) throws Exception { // 返回转账后余额，amount为正实数且不超过两位小数
        // System.err.println("Start: BankSystem.transfer()"); // debug
        BigDecimal ret = null;
        if (target == this.getAccount()) {
            throw new Exception("Cannot transfer to yourself!");
        }
        try {
            ret = this.ac.transfer(target, amount);
            this.result = "success";
            // System.err.println("Current balance: "+ret.toString());
        } catch (Exception e) {
            this.result = "failed";
            this.reason = e.getMessage();
            // System.err.println(this.reason);
            throw e;
        }
        // System.err.println("End: BankSystem.transfer()"); // debug
        return ret;
    }

    public void updateInfo(String name, String cipher, long phone, char sex) throws Exception {
        // System.err.println("Start: BankSystem.updateInfo()"); // debug
        try {
            cipher = client.SHA256(cipher); // 将密码转换为其SHA256结果以进行加密，防止明文密码传输
            this.ac.updateInfo(name, cipher, phone, sex);
            this.result = "success";
        } catch (Exception e) {
            this.result = "failed";
            this.reason = e.getMessage();
            // System.err.println(this.reason);
            throw e;
        }
        // System.err.println("End: BankSystem.updateInfo()"); // debug
    }

    public String registerAccountFromXls(String path) throws Exception {
        int len = path.length();
        if (!path.substring(len - 4, len).equals(".xls")) {
            throw new Exception("The file is not a xls file!");
        }
        String name = path.substring(0, path.length() - 4);
        Workbook book;
        WritableWorkbook res;
        Sheet sheet;
        WritableSheet resSheet;
        try {
        	book = Workbook.getWorkbook(new File(path));
        	sheet = book.getSheet(0);
            res = Workbook.createWorkbook(new File(name+"-res.xls"));
            resSheet = res.createSheet("Result", 0);
            
        } catch (Exception e) {
            this.result = "failed";
            this.reason = e.getMessage();
            // System.err.println(this.reason);
            throw e;
        }
        int cols = sheet.getColumns();
        int rows = sheet.getRows();
        if (cols != 7) {
            throw new Exception("The format of the xls file is illegal!");
        }
        for (int j = 0; j < cols; ++j) {
        	resSheet.addCell(new Label(j, 0, sheet.getCell(j, 0).getContents()));
        }
        resSheet.addCell(new Label(7, 0, "Result"));
        resSheet.addCell(new Label(8, 0, "Reason"));
        int cntSuccess = 0, cntFailed = 0;
        for (int i = 1; i < rows; ++i) {
            try {
                for (int j = 0; j < cols; ++j) {
                	resSheet.addCell(new Label(j, i, sheet.getCell(j, i).getContents()));
                }
                this.registerAccount(
                    this.checkAndConvertName(sheet.getCell(1, i).getContents()), 
                    this.checkAndConvertCipher(sheet.getCell(2, i).getContents()), 
                    this.checkAndConvertID(sheet.getCell(3, i).getContents()), 
                    this.checkAndConvertPhone(sheet.getCell(4, i).getContents()), 
                    this.checkAndConvertSex(sheet.getCell(5, i).getContents()), 
                    this.checkAndConvertDob(sheet.getCell(6, i).getContents())
                );
                resSheet.addCell(new Label(7, i, "success"));
                resSheet.addCell(new Label(8, i, "created"));
                ++cntSuccess;
            } catch (Exception ex) {
            	resSheet.addCell(new Label(7, i, "failed"));
            	resSheet.addCell(new Label(8, i, ex.getMessage()));
                ++cntFailed;
            }
        }
        try {
            res.write();
            res.close();
        } catch (Exception e) {
            throw e;
        }
        return String.format("Done: %d success, %d failed.", cntSuccess, cntFailed);
    }

    public void exportAccountInfo(String path) throws Exception {
        JSONObject res = null; // 存储请求结果
        JSONObject object = new JSONObject(); // 请求内容object
        object.put("type", "exportAccountInfo"); // 请求类型
        try {
            res = client.send(object);
        } catch (Exception e) {
            // System.err.println(e.getMessage());
            throw e;
        }
        if (res == null) {
            // System.err.println("No response from server!");
            this.result = "failed";
            this.reason = "No response from server!";
            throw new Exception(this.reason);
        }
        else{
            this.result = res.getString("result");
            this.reason = res.getString("reason");
            if (res.getString("result").contentEquals("success")) {
                long cntAccount = res.getLong("cntAccount");
                JSONArray accountsArray = res.getJSONArray("accounts");
                path += File.separator + "accounts.xls";
                WritableWorkbook resBook;
                WritableSheet resSheet;
                try {
                    resBook = Workbook.createWorkbook(new File(path));
                    resSheet = resBook.createSheet("Accounts", 0);
                    resSheet.addCell(new Label(0, 0, "Index"));
                    resSheet.addCell(new Label(1, 0, "Account ID"));
                    resSheet.addCell(new Label(2, 0, "Name"));
                    resSheet.addCell(new Label(3, 0, "Password/Cipher"));
                    resSheet.addCell(new Label(4, 0, "ID"));
                    resSheet.addCell(new Label(5, 0, "Phone Number"));
                    resSheet.addCell(new Label(6, 0, "Sex"));
                    resSheet.addCell(new Label(7, 0, "Date of birth"));
                    resSheet.addCell(new Label(8, 0, "Balance"));
                    resSheet.addCell(new Label(9, 0, "Closed"));
                    resSheet.addCell(new Label(10, 0, "Open year"));
                    for (int i = 0; i < cntAccount; ++i) {
                        JSONObject account = accountsArray.getJSONObject(i);
                        resSheet.addCell(new Label(0, i+1, String.format("%d", i+1)));
                        resSheet.addCell(new Label(1, i+1, account.getString("aid")));
                        resSheet.addCell(new Label(2, i+1, account.getString("name")));
                        resSheet.addCell(new Label(3, i+1, account.getString("cipher")));
                        resSheet.addCell(new Label(4, i+1, account.getString("id")));
                        resSheet.addCell(new Label(5, i+1, account.getString("phone")));
                        resSheet.addCell(new Label(6, i+1, account.getString("sex")));
                        resSheet.addCell(new Label(7, i+1, account.getString("dob")));
                        resSheet.addCell(new Label(8, i+1, account.getString("balance")));
                        resSheet.addCell(new Label(9, i+1, account.getString("closed")));
                        resSheet.addCell(new Label(10, i+1, account.getString("openyear")));
                    }
                    resBook.write();
                    resBook.close();
                } catch (Exception ex) {
                    throw ex;
                }
            }
            else {
                throw new Exception(this.reason);
            }
        }
    }

    public void exportReport(String path) throws Exception {
        JSONObject res = null; // 存储请求结果
        JSONObject object = new JSONObject(); // 请求内容object
        object.put("type", "exportReport"); // 请求类型
        try {
            res = client.send(object);
        } catch (Exception e) {
            // System.err.println(e.getMessage());
            throw e;
        }
        if (res == null) {
            // System.err.println("No response from server!");
            this.result = "failed";
            this.reason = "No response from server!";
            throw new Exception(this.reason);
        }
        else{
            this.result = res.getString("result");
            this.reason = res.getString("reason");
            if (res.getString("result").contentEquals("success")) {
                String totalCntAccounts = res.getString("totalCntAccounts");
                String newAccountsThisYear = res.getString("newAccountsThisYear");
                String totalBalance = res.getString("totalBalance");
                path += File.separator + "bank-report.pdf";
                Document document = new Document();
                PdfWriter.getInstance(document, new FileOutputStream(path));
                try {
                	document.open();
                    document.add(new Paragraph(String.format("Feima Bank now has %s users and total balance of %s. In %d, there are %s new users created their account.", totalCntAccounts, totalBalance, LocalDate.now().getYear(), newAccountsThisYear)));
                    document.close();
                } catch (Exception ex) {
                    throw ex;
                }
            }
            else {
                throw new Exception(this.reason);
            }
        }
    }

    public void logout() {
        this.ac = null;
        this.adminName = null;
    }
}
